package org.apache.ocean;

import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.commons.lang.builder.*;
import java.util.List;
import org.apache.commons.lang.*;


/** A hash key encapsulating a query, a list of filters, and a sort
 * @author yonik
 * @version $Id: QueryResultKey.java 472574 2006-11-08 18:25:52Z yonik $
 */
public final class QueryKey {
  final Query query;
  final Sort sort; // change to normal Sort after Lucene 1.4.3
  final SortField[] sortFields;
  final List<Query> filters;
  public Integer start;
  public Integer limit;
  public List<DocFreq> docFreqs;
  final int nc_flags;  // non-comparable flags... ignored by hashCode and equals
  private final int hashCode;  // cached hashCode
  private static SortField[] defaultSort = new SortField[0];
  
  public QueryKey(Query query, List<Query> filters, Sort sort, Integer start, Integer limit, List<DocFreq> docFreqs, int nc_flags) {
    this.query = query;
    this.filters = filters;
    this.sort = sort;
    this.start = start;
    this.limit = limit;
    this.docFreqs = docFreqs;
    this.nc_flags = nc_flags;
    
    if (this.sort != null) this.sortFields = sort.getSort();
    else this.sortFields = defaultSort;
    
    HashCodeBuilder builder = new HashCodeBuilder(49, 1001);
    builder.append(query);
    builder.append(start);
    builder.append(limit);
    if (docFreqs != null) {
      for (DocFreq docFreq : docFreqs) {
        builder.append(docFreqs.hashCode());
      }
    }
    if (filters != null) builder.append(filters.toArray());
    for (SortField sortField : sortFields) {
      // mix the bits so that sortFields are position dependent
      // so that a,b won't hash to the same value as b,a
      //hashCode ^= (hashCode << 8) | (hashCode >>> 25);   // reversible hash
      builder.append(sortField.getField());
      builder.append(sortField.getType());
      builder.append(sortField.getReverse());
      //if (sortField.getReverse()) hashCode = ~hashCode;
      builder.append(sortField.getLocale());
      builder.append(sortField.getFactory());
    }
    hashCode = builder.toHashCode();
    /**
     * int hashCode = query.hashCode();
     *
     * if (filters != null) hashCode ^= filters.hashCode();
     *
     *
     * if (start != null) hashCode ^= start.intValue();
     * if (limit != null) hashCode ^= limit.intValue();
     *
     * for (SortField sortField : sortFields) {
     * // mix the bits so that sortFields are position dependent
     * // so that a,b won't hash to the same value as b,a
     * hashCode ^= (hashCode << 8) | (hashCode >>> 25);   // reversible hash
     *
     * if (sortField.getField() != null) hashCode += sortField.getField().hashCode();
     * hashCode += sortField.getType();
     * if (sortField.getReverse()) hashCode = ~hashCode;
     * if (sortField.getLocale() != null) hashCode += sortField.getLocale().hashCode();
     * if (sortField.getFactory() != null) hashCode += sortField.getFactory().hashCode();
     * }
     * this.hashCode = hashCode;
     **/
  }
  
  public int hashCode() {
    return hashCode;
  }
  
  public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof QueryKey)) return false;
    QueryKey other = (QueryKey)o;
    
    // fast check of the whole hash code... most hash tables will only use
    // some of the bits, so if this is a hash collision, it's still likely
    // that the full cached hash code will be different.
    if (this.hashCode != other.hashCode) return false;
    
    // check for the thing most likely to be different (and the fastest things)
    // first.
    if (this.sortFields.length != other.sortFields.length) return false;
    if (!this.query.equals(other.query)) return false;
    if (!isEqual(this.filters, other.filters)) return false;
    
    if (docFreqs != null) {
      if (docFreqs.size() != other.docFreqs.size()) return false;
      if (!docFreqs.equals(other.docFreqs)) return false;
    }
    
    for (int i=0; i < sortFields.length; i++) {
      SortField sf1 = this.sortFields[i];
      SortField sf2 = other.sortFields[i];
      if (sf1.getType() != sf2.getType()) return false;
      if (sf1.getReverse() != sf2.getReverse()) return false;
      if (!isEqual(sf1.getField(), sf2.getField())) return false;
      if (!isEqual(sf1.getLocale(), sf2.getLocale())) return false;
      if (!isEqual(sf1.getFactory(), sf2.getFactory())) return false;
      // NOTE: the factory must be identical!!! use singletons!
    }
    return true;
  }
  
  private static boolean isEqual(Object o1, Object o2) {
    if (o1 == o2) return true;  // takes care of identity and null cases
    if (o1 == null || o2 == null) return false;
    return o1.equals(o2);
  }
}
